﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using CEED.Utils;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace CEED_Mini
{
    public partial class Form1 : Form
    {
        Thread tSaveCode;
        Thread tSaveRomFS;

        public static List<string> xorfilepaths = new List<string>();
        public const int MediaUnitSize = 0x200;
        public string outpath;

        public NCCH CXI;
        public ExeFS ExeFs;
        public long NcchOffset;

        SEEDinfo seed;
        public Form1()
        {
            InitializeComponent();
            CB_Advance.Checked = false;

            SavedRomFS += Form1_SavedRomFS;
            SavingRomFS += Form1_SavingRomFS;
        }


        public void Initial()
        {
            CXI = null;
            ExeFs = null;
            NcchOffset = 0;

            ClearErrorMessage();
            PB_1.Visible = false;
            PB_1.Maximum = 0;
            PB_1.Value = 0;
        }
        public string GetXorFilePath(string sectioname)
        {
            string result = "";
            string xorpad = string.Format("{0:X16}.Main.{1}.xorpad", BitConverter.ToUInt64(CXI.Header.TitleID, 0), sectioname).ToUpper();
            foreach (string s in xorfilepaths)
            {
                if (s.ToUpper().Contains(xorpad))
                {
                    result = s;
                    break;
                }
            }
            if (string.IsNullOrEmpty(result))
                throw new Exception(string.Format("Missing xorpad:{0}", xorpad));
            if (!File.Exists(result))
                throw new Exception(string.Format("{0} doesn't exist.", result));
            return result;
        }
        private void SetXioFiles(string raw)
        {
            string[] temp = raw.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            bool existed;
            foreach (string s in temp)
            {
                existed = false;
                for (int i = 0; i < xorfilepaths.Count; i++)
                {
                    if (xorfilepaths[i].Contains(s))
                    {
                        existed = true;
                    }
                }
                if (!existed)
                {
                    xorfilepaths.Add(s);
                }
            }
        }
        public string AddXorpads(string[] paths)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < paths.Length; i++)
            {
                string format = Path.GetExtension(paths[i]).ToUpper();
                if (format == ".XORPAD")
                {
                    xorfilepaths.Add(paths[i]);
                    sb.AppendFormat("{0};", Path.GetFileName(paths[i]));
                }
            }
            return sb.ToString();
        }

        public void LoadFile(string path,long ncchoffset)
        {
            FileStream fs = File.OpenRead(path);
            fs.Position = ncchoffset;

            if (fs.Position >= fs.Length - Marshal.SizeOf(typeof(NCCHHeader)) + 0x100)
            {
                PrintErrorMessage("Invalid file");
                return;
            }
            CXI = new NCCH(fs);

            long ExefsSize = CaculateMediaUnite(CXI.Header.ExeFSLength);
            byte[] ExheaderRaw = new byte[0x800];
            byte[] ExeFSRaw = new byte[ExefsSize];
            fs.Read(ExheaderRaw, 0, ExheaderRaw.Length);
            fs.Position = NcchOffset + CaculateMediaUnite(CXI.Header.ExeFSOffset);
            fs.Read(ExeFSRaw, 0, ExeFSRaw.Length);

            using (MemoryStream exhstream = new MemoryStream(ExheaderRaw))
            {
                switch (CXI.Header.Flags[7])
                {
                    case 0:
                    case 0x20:
                        {
                            if (!CB_Advance.Checked)
                                throw new Exception("Use Advance Options to decrypt this file.");
                            if (string.IsNullOrEmpty(TB_Xor.Text))
                                return;
                            string exhxor = GetXorFilePath("exheader");
                            using (FileStream xorfs = File.OpenRead(exhxor))
                            {
                                using (MemoryStream decstream = new MemoryStream())
                                {
                                    Xorer.TramsfoemStream(decstream, exhstream, xorfs, 0, exhstream.Length);
                                    decstream.Position = 0;
                                    CXI.ExHeader = StructReader.ReadStruct<NCCHExtendedHeader>(decstream);
                                    CXI.AccessControlExtend = StructReader.ReadStruct<NCCHAccessControlExtend>(decstream);
                                }
                            }

                            string exefsnorm = GetXorFilePath("exefs_norm");
                            using (FileStream xorfs = File.OpenRead(exefsnorm))
                            {
                                using (MemoryStream decstream = new MemoryStream())
                                {
                                    Xorer.TramsfoemStream(decstream, new MemoryStream(ExeFSRaw), xorfs, 0, ExefsSize);
                                    decstream.Position = 0;
                                    ExeFs = new ExeFS(decstream);
                                    if (CXI.Header.Flags[3] == 1)
                                    {
                                        decstream.Position = 0;
                                        xorfs.Position = 0;
                                        Xorer.TramsfoemStream(decstream, new MemoryStream(ExeFSRaw), xorfs, 0, Marshal.SizeOf(typeof(ExeFSHeader)));
                                        using (FileStream xor7xfs = File.OpenRead(GetXorFilePath("exefs_7x")))
                                        {
                                            Xorer.TramsfoemStream(decstream, new MemoryStream(ExeFSRaw), xor7xfs, Marshal.SizeOf(typeof(ExeFSHeader)), ExeFs.Header.HeaderEntries[0].size);
                                        }
                                        xorfs.Position = 0;
                                        long TopLength = Marshal.SizeOf(typeof(ExeFSHeader)) + ExeFs.Header.HeaderEntries[0].size;
                                        Xorer.TramsfoemStream(decstream, new MemoryStream(ExeFSRaw), xorfs, TopLength, ExeFSRaw.Length - TopLength);
                                        decstream.Position = 0;
                                        ExeFs = new ExeFS(decstream);
                                    }
                                }
                            }
                            break;
                        }
                    case 1:
                    case 0x21:
                        {
                            byte[] aesctr = new byte[16];
                            NCCHInfo.GetNcchAesCounter(ref aesctr, CXI, NcchSection.exheader);
                            using (MemoryStream decstream = new MemoryStream())
                            {
                                Aes128Ctr.TransformFile(new byte[16], aesctr, decstream, exhstream, 0, exhstream.Length);
                                decstream.Position = 0;
                                CXI.ExHeader = StructReader.ReadStruct<NCCHExtendedHeader>(decstream);
                                CXI.AccessControlExtend = StructReader.ReadStruct<NCCHAccessControlExtend>(decstream);
                            }

                            NCCHInfo.GetNcchAesCounter(ref aesctr, CXI, NcchSection.exefs);
                            using (MemoryStream decstream = new MemoryStream())
                            {
                                Aes128Ctr.TransformFile(new byte[16], aesctr, decstream, new MemoryStream(ExeFSRaw), 0, ExeFSRaw.Length);
                                decstream.Position = 0;
                                ExeFs = new ExeFS(decstream);
                            }
                            break;
                        }
                    case 4:
                    case 5:
                        {
                            CXI.ExHeader = StructReader.ReadStruct<NCCHExtendedHeader>(exhstream);
                            CXI.AccessControlExtend = StructReader.ReadStruct<NCCHAccessControlExtend>(exhstream);
                            ExeFs = new ExeFS(new MemoryStream(ExeFSRaw));
                            break;
                        }
                }
            }
            fs.Close();
            ExeFs.SavedCode += ExeFs_SavedCode;
            ExeFs.SavingCode += ExeFs_SavingCode;
            ExeFs.DecompressingCode += ExeFs_DecompressingCode;
            ExeFs.DecompressedCode += ExeFs_DecompressedCode;
        }

        public void PrintStatusMessage(string m)
        {
            TSSL_Status.BackColor = Color.FromKnownColor(KnownColor.Control);
            TSSL_Status.Text = m;
        }
        public void PrintErrorMessage(string er)
        {
            TSSL_Status.BackColor = Color.LightPink;
            TSSL_Status.Text = er;
        }
        public void ClearErrorMessage()
        {
            TSSL_Status.Text = "";
            TSSL_Status.BackColor = Color.FromKnownColor(KnownColor.Control);
        }
        public void UpdateProcessbar(int value)
        {
            if (this.InvokeRequired)
            {
                SetValueCallback s = new SetValueCallback(UpdateProcessbar);
                this.Invoke(s, new object[] { value });
            }
            else
                PB_1.Value += value;
        }
        public void InitProcessbar(int value)
        {
            if (InvokeRequired)
            {
                SetValueCallback s = new SetValueCallback(InitProcessbar);
                Invoke(s, new object[] { value });
            }
            else
            {
                PB_1.Value = value;
                PB_1.Maximum = value;
                PB_1.Visible = false;
            }
        }

        public void SaveRomFS(object para)
        {
            SaveRomFSPara P = (SaveRomFSPara)para;
            P.SourceStream.Position = P.NCCHOffset;
            BinaryReader reader = new BinaryReader(P.SourceStream);
            try
            {
                using (FileStream dstfs = File.Create(P.Path))
                {
                    using (BinaryWriter writer = new BinaryWriter(dstfs))
                    {
                        long romfsoffset = CaculateMediaUnite(CXI.Header.RomFSOffset);
                        long romfssize = CaculateMediaUnite(CXI.Header.RomFSLength);
                        P.SourceStream.Seek(romfsoffset, SeekOrigin.Current);
                        RomFS romfs;
                        SavingRomFS(this, EventArgs.Empty);
                        switch (CXI.Header.Flags[7])
                        {
                            case 0:
                            case 0x20:
                                {
                                    string romfsxor = GetXorFilePath("romfs");
                                    using (FileStream xorfs = File.OpenRead(romfsxor))
                                    {
                                        using (MemoryStream ms = new MemoryStream())
                                        {
                                            Xorer.TramsfoemStream(ms, P.SourceStream, xorfs, 0, 0x60);
                                            ms.Position = 0;
                                            romfs = new RomFS(ms);
                                            P.SourceStream.Seek(-0x60, SeekOrigin.Current);
                                            xorfs.Seek(-0x60, SeekOrigin.Current);
                                        }
                                        long size = Align(romfs.Header.Level3.Size, 1 << 0xC);//s_nBlockSizePower = 0xC;s_nBlockSize = 1 << s_nBlockSizePower;
                                        switch (P.Method)
                                        {
                                            case SaveRomFSMethod.Level3Only:
                                                {
                                                    P.SourceStream.Seek(0x1000, SeekOrigin.Current);
                                                    xorfs.Seek(0x1000, SeekOrigin.Current);
                                                    break;
                                                }
                                            case SaveRomFSMethod.IVFCandLevel3:
                                                {
                                                    size += 0x1000;
                                                    break;
                                                }
                                            case SaveRomFSMethod.Full:
                                                {
                                                    size = 0x1000 + romfs.Header.Level1.Size + romfs.Header.Level2.Size + romfs.Header.Level3.Size;
                                                    break;
                                                }
                                        }
                                        Xorer.TramsfoemStream(dstfs, P.SourceStream, xorfs, 0, size);
                                    }
                                    break;
                                }
                            case 1:
                            case 0x21:
                                {
                                    byte[] aesctr = new byte[16];
                                    NCCHInfo.GetNcchAesCounter(ref aesctr, CXI, NcchSection.romfs);
                                    using (MemoryStream ms = new MemoryStream())
                                    {
                                        Aes128Ctr.TransformFile(new byte[16], aesctr, ms, P.SourceStream, 0, 0x60);
                                        ms.Position = 0;
                                        romfs = new RomFS(ms);
                                        P.SourceStream.Seek(-0x60, SeekOrigin.Current);
                                    }
                                    long size = Align(romfs.Header.Level3.Size, 1 << 0xC); //s_nBlockSizePower = 0xC;s_nBlockSize = 1 << s_nBlockSizePower;
                                    switch (P.Method)
                                    {
                                        case SaveRomFSMethod.Level3Only:
                                            {
                                                Aes128Ctr.TransformFile(new byte[16], aesctr, dstfs, P.SourceStream, 0x1000, size);
                                                break;
                                            }
                                        case SaveRomFSMethod.IVFCandLevel3:
                                            {
                                                size += 0x1000;
                                                Aes128Ctr.TransformFile(new byte[16], aesctr, dstfs, P.SourceStream, 0, size);
                                                break;
                                            }
                                        case SaveRomFSMethod.Full:
                                            {
                                                size = 0x1000 + romfs.Header.Level1.Size + romfs.Header.Level2.Size + romfs.Header.Level3.Size;
                                                Aes128Ctr.TransformFile(new byte[16], aesctr, dstfs, P.SourceStream, 0, size);
                                                break;
                                            }
                                    }
                                    break;
                                }
                            case 4:
                            case 5:
                                {
                                    romfs = new RomFS(P.SourceStream);
                                    long size = Align(romfs.Header.Level3.Size, 1 << 0xC); //s_nBlockSizePower = 0xC;s_nBlockSize = 1 << s_nBlockSizePower;
                                    long offset = 0;
                                    switch (P.Method)
                                    {
                                        case SaveRomFSMethod.Level3Only:
                                            {
                                                offset = 0x1000;
                                                break;
                                            }
                                        case SaveRomFSMethod.IVFCandLevel3:
                                            {
                                                offset = 0;
                                                size += 0x1000;
                                                break;
                                            }
                                        case SaveRomFSMethod.Full:
                                            {
                                                offset = 0;
                                                size = 0x1000 + romfs.Header.Level1.Size + romfs.Header.Level2.Size + romfs.Header.Level3.Size;
                                                break;
                                            }
                                    }

                                    P.SourceStream.Seek(offset - Marshal.SizeOf(typeof(RomFSHeader)), SeekOrigin.Current);
                                    long buffersize = 1024 * 1024 * 4;
                                    byte[] buffer;
                                    while (size > 0)
                                    {
                                        buffersize = size > buffersize ? buffersize : size;
                                        buffer = new byte[buffersize];
                                        buffer = reader.ReadBytes((int)buffersize);
                                        writer.Write(buffer);
                                        size -= buffersize;
                                    }
                                    break;
                                }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                PrintErrorMessage(ex.Message);
            }
            SavedRomFS(this, EventArgs.Empty);
        }
        public static long CaculateMediaUnite(uint NumInMediaUnit)
        {
            return NumInMediaUnit * MediaUnitSize;
        }
        public static long Align(long Value, long Alignment)
        {
            return (Value + Alignment - 1) / Alignment * Alignment;
        }

        private void B_GN_Click(object sender, EventArgs e)
        {
            if (CXI == null)
                return;
            NCCHInfo ncchinfo;
            try
            {
                if (RB_RD.Checked)
                    ncchinfo = new NCCHInfo(CXI, 0, Encoding.UTF8);
                else if (RB_3MD.Checked)
                    ncchinfo = new NCCHInfo(CXI, 0, Encoding.Unicode);
                else
                    return;
                SaveFileDialog dialog = new SaveFileDialog();
                dialog.Filter = "Binary files|*.bin";
                dialog.FileName = "ncchinfo";
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    ncchinfo.ToFile(dialog.FileName);
                }
            }
            catch(Exception ex)
            {
                PrintErrorMessage(ex.Message);
            }
        }
        private void B_AddXor_Click(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Filter = "Xorpads|*.xorpad";
            dialog.Multiselect = true;
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                TB_Xor.Text = AddXorpads(dialog.FileNames);
            }
        }
        private void B_XorClear_Click(object sender, EventArgs e)
        {
            TB_Xor.Text = string.Empty;
            xorfilepaths = new List<string>();
        }
        private void B_Output_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(outpath))
            {
                Process pro = new Process();
                pro.StartInfo.FileName = "explorer";
                pro.StartInfo.Arguments = @"/select," + outpath;

                pro.Start();
            }
        }
        private void CB_Advance_CheckedChanged(object sender, EventArgs e)
        {
            ClearErrorMessage();
            if (CB_Advance.Checked)
            {
                this.Height = 294;
                GB_Advance.Visible = true;
            }
            else
            {
                GB_Advance.Visible = false;
                this.Height = 155;
            }
        }
        private void RB_RD_MouseHover(object sender, EventArgs e)
        {
            TT_RX.Show("  rxTools/Decrypt9", this.RB_RD);
        }
        private void RB_3MD_MouseHover(object sender, EventArgs e)
        {
            TT_3MD.Show("  3DS Multi Decryptor", this.RB_3MD);
        }
        private void TB_Xor_Enter(object sender, EventArgs e)
        {
            TT_Xor.Show("Separate file names by \";\"", this.TB_Xor);
        }
        private void B_OpenFile_Click(object sender, EventArgs e)
        {
            ClearErrorMessage();
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Filter = "CCI/CIA|*.3ds;*.cci;*.cia|CXI|*.cxi";
            dialog.Multiselect = false;
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                CB_FileList.Items.Add(dialog.FileName);
                CB_FileList.Text = dialog.FileName;
            }
        }
        private void CB_FileList_TextChanged(object sender, EventArgs e)
        {
            try
            {
                Initial();
                string format = Path.GetExtension(CB_FileList.Text).ToUpper();
                switch (format)
                {
                    case ".CIA":
                        {
                            using(FileStream fs=File.OpenRead(CB_FileList.Text))
                            {
                                using (BinaryReader reader=new BinaryReader(fs))
                                {
                                    NcchOffset = Align(reader.ReadUInt32(), 0x40);
                                    fs.Seek(4, SeekOrigin.Current);
                                    NcchOffset += Align(reader.ReadUInt32(), 0x40) + Align(reader.ReadUInt32(), 0x40) + Align(reader.ReadUInt32(), 0x40);
                                }
                            }
                            break;
                        }
                    case ".CCI":
                    case ".3DS":
                        {
                            using(FileStream fs=File.OpenRead(CB_FileList.Text))
                            {
                                NCSD ncsd = new NCSD(fs);
                                NcchOffset = CaculateMediaUnite(ncsd.Header.CXIEntries[0].Offset);
                            }
                            break;
                        }
                    case ".CXI":
                        {
                            NcchOffset = 0;
                            break;
                        }
                }
                LoadFile(CB_FileList.Text, NcchOffset);
            }
            catch (Exception ex)
            {
                PrintErrorMessage(ex.Message);
            }
        }
        private void B_Extract_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(CB_FileList.Text))
            {
                try
                {
                    if (((xorfilepaths.Count == 0) || xorfilepaths == null) && CXI.Header.Flags[7] == 0)
                    {
                        PrintErrorMessage("Please add xorpads");
                        return;
                    }
                    LoadFile(CB_FileList.Text, NcchOffset);
                    if ((ExeFs == null) && (CXI.Header.Flags[7] == 0))
                    {
                        PrintErrorMessage("exefs.xorpad didn't added");
                        return;
                    }
                    if (ExeFs == null)
                    {
                        PrintErrorMessage("Cannot find exefs");
                        return;
                    }
                    FolderBrowserDialog dialog = new FolderBrowserDialog();
                    if (!string.IsNullOrEmpty(outpath))
                        dialog.SelectedPath = outpath;
                    dialog.ShowNewFolderButton = true;
                    if (dialog.ShowDialog() == DialogResult.OK)
                    {
                        SaveMethodDialog smf = new SaveMethodDialog();
                        if (smf.ShowDialog() == DialogResult.OK)
                        {
                            PrintStatusMessage("Processing...");
                            PB_1.Maximum = 2;
                            PB_1.Visible = true;

                            outpath = dialog.SelectedPath;
                            try
                            {
                                string exefsname = string.Format("{0:X16}.code", BitConverter.ToUInt64(CXI.Header.TitleID, 0)).Substring(8);
                                if ((CXI.ExHeader.CodeSetInfo.Flags.Flag & 1) == 1)
                                {
                                    PB_1.Maximum++;
                                    tSaveCode = new Thread(new ParameterizedThreadStart(ExeFs.SaveCode));
                                    SaveCodePara scp = new SaveCodePara(string.Format("{0}\\{1}", dialog.SelectedPath, exefsname), true);
                                    tSaveCode.Start(scp);
                                }
                                else
                                {
                                    tSaveCode = new Thread(new ParameterizedThreadStart(ExeFs.SaveCode));
                                    SaveCodePara scp = new SaveCodePara(string.Format("{0}\\{1}", dialog.SelectedPath, exefsname), false);
                                    tSaveCode.Start(scp);
                                }
                                string romfsname = string.Format("{0:X16}.romfs", BitConverter.ToUInt64(CXI.Header.TitleID, 0)).Substring(8);
                                SaveRomFSPara srp = new SaveRomFSPara(string.Format("{0}\\{1}", dialog.SelectedPath, romfsname), File.OpenRead(CB_FileList.Text), NcchOffset, smf.SaveMethod);
                                tSaveRomFS = new Thread(new ParameterizedThreadStart(this.SaveRomFS));
                                tSaveRomFS.Start(srp);
                            }
                            catch (Exception ex)
                            {
                                PrintErrorMessage(string.Format("Fail，{0}", ex.Message));
                            }
                        }
                    }
                }
                catch(Exception ex)
                {
                    PrintErrorMessage(ex.Message);
                }

            }
        }
        private void RB_RD_CheckedChanged(object sender, EventArgs e)
        {
            B_GN.Enabled = true;
        }
        private void TB_Xor_TextChanged(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(TB_Xor.Text))
                return;
            SetXioFiles(TB_Xor.Text);
            ClearErrorMessage();
        }
        private void AboutLabel_Click(object sender, EventArgs e)
        {
            AboutForm af = new AboutForm();
            af.ShowDialog();
        }
        private void CB_FileList_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy;
        }
        private void CB_FileList_DragDrop(object sender, DragEventArgs e)
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
            string format = Path.GetExtension(files[0]).ToUpper();
            if ((format == ".CIA") || (format == ".3DS") || (format == ".3DZ") || (format == ".CXI"))
            {
                CB_FileList.Items.Add(files[0]);
                CB_FileList.Text = files[0];
            }
        }
        private void TB_Xor_DragDrop(object sender, DragEventArgs e)
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
            TB_Xor.Text = AddXorpads(files);
        }
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if ((tSaveCode == null) || (tSaveRomFS == null))
                return;
            if ((tSaveCode.ThreadState == System.Threading.ThreadState.Running) || (tSaveRomFS.ThreadState == System.Threading.ThreadState.Running))
            {
                if (MessageBox.Show("Program is running, really exit?", "Warnning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
                {
                    Environment.Exit(0);
                    return;
                }
                else
                {
                    e.Cancel = true;
                }
            }
        }
        void Form1_SavingRomFS(object sender, EventArgs e)
        {
            PrintStatusMessage("Saving RomFS...");
        }
        void Form1_SavedRomFS(object sender, EventArgs e)
        {
            UpdateProcessbar(1);
            ClearErrorMessage();
            if (tSaveCode.ThreadState == System.Threading.ThreadState.Stopped)
            {
                PrintStatusMessage("Done!");
                InitProcessbar(0);
            }
        }
        void ExeFs_SavingCode(object sender, EventArgs e)
        {
            PrintStatusMessage("Saving .code...");
        }
        void ExeFs_SavedCode(object sender, EventArgs e)
        {
            UpdateProcessbar(1);
            ClearErrorMessage();
            if(tSaveRomFS.ThreadState==System.Threading.ThreadState.Stopped)
            {
                PrintStatusMessage("Done!");
                InitProcessbar(0);
            }
        }
        void ExeFs_DecompressedCode(object sender, EventArgs e)
        {
            UpdateProcessbar(1);
            ClearErrorMessage();
        }
        void ExeFs_DecompressingCode(object sender, EventArgs e)
        {
            PrintStatusMessage("Decompressing .code...");
        }

        public delegate void SetValueCallback(int value);
        public delegate void RomFSSavingHandler(object sender, EventArgs e);
        public delegate void RomFSSavedHandler(object sender, EventArgs e);
        public event RomFSSavingHandler SavingRomFS;
        public event RomFSSavedHandler SavedRomFS;
    }
    public enum SaveRomFSMethod
    {
        IVFCandLevel3,
        Level3Only,
        Full,
    }
    public class SaveRomFSPara
    {
        public string Path;
        public Stream SourceStream;
        public long NCCHOffset;
        public SaveRomFSMethod Method;

        public SaveRomFSPara(string path, Stream srcfs, long ncchoffset, SaveRomFSMethod method)
        {
            Path = path;
            SourceStream = srcfs;
            this.NCCHOffset = ncchoffset;
            this.Method = method;
        }
    }
}
